/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import Unsafe from './Unsafe';
import Util from './Util';
import Constants from './Constants';
import NumberTransform from './NumberTransform'
import Long from '../util/long/index'

export default class SequenceStore {
    public literalsBuffer: Int8Array;
    public literalsLength: number;
    public offsets: Int32Array;
    public literalLengths: Int32Array;
    public matchLengths: Int32Array;
    public sequenceCount: number;
    public literalLengthCodes: Int8Array;
    public matchLengthCodes: Int8Array;
    public offsetCodes: Int8Array;
    public longLengthField: LongField;
    public longLengthPosition: number;
    private static LITERAL_LENGTH_CODE: Int8Array = new Int8Array([0, 1, 2, 3, 4, 5, 6, 7,
    8, 9, 10, 11, 12, 13, 14, 15,
    16, 16, 17, 17, 18, 18, 19, 19,
    20, 20, 20, 20, 21, 21, 21, 21,
    22, 22, 22, 22, 22, 22, 22, 22,
    23, 23, 23, 23, 23, 23, 23, 23,
    24, 24, 24, 24, 24, 24, 24, 24,
    24, 24, 24, 24, 24, 24, 24, 24]);
    private static MATCH_LENGTH_CODE: Int8Array = new Int8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
    16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
    32, 32, 33, 33, 34, 34, 35, 35, 36, 36, 36, 36, 37, 37, 37, 37,
    38, 38, 38, 38, 38, 38, 38, 38, 39, 39, 39, 39, 39, 39, 39, 39,
    40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
    41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41,
    42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
    42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42]);

    constructor(blockSize: number, maxSequences: number) {
        this.offsets = new Int32Array(maxSequences);
        this.literalLengths = new Int32Array(maxSequences);
        this.matchLengths = new Int32Array(maxSequences);

        this.literalLengthCodes = new Int8Array(maxSequences);
        this.matchLengthCodes = new Int8Array(maxSequences);
        this.offsetCodes = new Int8Array(maxSequences);

        this.literalsBuffer = new Int8Array(blockSize);

        this.reset();
    }

    public appendLiterals(inputBase: any, inputAddress: Long, inputSize: number): void
    {
        Unsafe.copyMemory(inputBase, inputAddress.toNumber(), this.literalsBuffer, Unsafe.ARRAY_BYTE_BASE_OFFSET + this.literalsLength, Long.fromNumber(inputSize));
        this.literalsLength += inputSize;
    }

    public storeSequence(literalBase: any, literalAddress: Long, literalLength: number, offsetCode: number, matchLengthBase: number): void
    {
        let input: Long = literalAddress;
        let output: Long = Long.fromNumber(Unsafe.ARRAY_BYTE_BASE_OFFSET + this.literalsLength);
        let copied: number = 0;
        do {
            Unsafe.putLong(this.literalsBuffer, output.toNumber(), Unsafe.getLong(literalBase, input.toNumber()));
            input = input.add(Constants.SIZE_OF_LONG);
            output = output.add(Constants.SIZE_OF_LONG);
            copied += Constants.SIZE_OF_LONG;
        }
        while (copied < literalLength);

        this.literalsLength += literalLength;

        if (literalLength > 65535) {
            this.longLengthField = LongField.LITERAL;
            this.longLengthPosition = this.sequenceCount;
        }
        this.literalLengths[this.sequenceCount] = literalLength;

        this.offsets[this.sequenceCount] = offsetCode + 1;

        if (matchLengthBase > 65535) {
            this.longLengthField = LongField.MATCH;
            this.longLengthPosition = this.sequenceCount;
        }

        this.matchLengths[this.sequenceCount] = matchLengthBase;

        this.sequenceCount++;
    }

    public reset(): void {
        this.literalsLength = 0;
        this.sequenceCount = 0;
        this.longLengthField = null;
    }

    public generateCodes(): void {
        for (let i: number = 0; i < this.sequenceCount; ++i) {
            this.literalLengthCodes[i] = NumberTransform.toByte(SequenceStore.literalLengthToCode(this.literalLengths[i]));
            this.offsetCodes[i] = NumberTransform.toByte(Util.highestBit(this.offsets[i]));
            this.matchLengthCodes[i] = NumberTransform.toByte(SequenceStore.matchLengthToCode(this.matchLengths[i]));
        }

        if (this.longLengthField == LongField.LITERAL) {
            this.literalLengthCodes[this.longLengthPosition] = Constants.MAX_LITERALS_LENGTH_SYMBOL;
        }
        if (this.longLengthField == LongField.MATCH) {
            this.matchLengthCodes[this.longLengthPosition] = Constants.MAX_MATCH_LENGTH_SYMBOL;
        }
    }

    private static literalLengthToCode(literalLength: number): number
    {
        if (literalLength >= 64) {
            return Util.highestBit(literalLength) + 19;
        }
        else {
            return SequenceStore.LITERAL_LENGTH_CODE[literalLength];
        }
    }

    private static matchLengthToCode(matchLengthBase: number): number
    {
        if (matchLengthBase >= 128) {
            return Util.highestBit(matchLengthBase) + 36;
        }
        else {
            return SequenceStore.MATCH_LENGTH_CODE[matchLengthBase];
        }
    }
}

enum LongField {
    LITERAL, MATCH
}